﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Threading;
using System.Data.SqlTypes;
using Clotho.UI;

namespace Clotho {
	public class Novel:IEnumerable<Plot>{
		public readonly FeatureFactory Feature_factory = new FeatureFactory();
		private readonly CmdRecord Cmd_record = new CmdRecord();
		public Plot this[string name]=> Plots.ContainsKey(name)?Plots[name]:null;
		public readonly Dictionary<string, Plot> Plots = new Dictionary<string, Plot>();

		public IEnumerator<Plot> GetEnumerator() {
			return Plots.Values.GetEnumerator();
		}
		IEnumerator IEnumerable.GetEnumerator() {
			return Plots.Values.GetEnumerator();
		}
		public Plot Base_plot { get;private set; } = null;
		public string File_path { get; set; } = null;

		public readonly List<UI.ControlKeeper> Topic_Control_keeps = new List<UI.ControlKeeper>(128);
		private Novel() {
			Feature_factory.registerFeature(typeof(Novelview.ViewFeature));
			Feature_factory.registerFeature(typeof(Keyword.KeywordFeature));
			Feature_factory.registerFeature(typeof(Keyword.KeywordDescFeature));
			Feature_factory.registerFeature(typeof(Novelview.NovelViewFeature));
			Ui_control_enumable = new UiControlEnumable(this);
			PlotTable.PlotTableControl.injectTo(Topic_Control_keeps, this);

		}
		public class UiControlEnumable : IEnumerable<UI.ControlKeeper> {
			Novel nov;
			public UiControlEnumable(Novel nov) {
				this.nov = nov;
			}
			public IEnumerator<ControlKeeper> GetEnumerator() {
				foreach (var k in nov.Topic_Control_keeps) {
					yield return k;
				}
				foreach (var fea in nov.Base_plot.Features) {
					var ui_fea = fea as UI.IUiFeature;
					if (ui_fea != null) {
						foreach (var k in ui_fea.Create_control_table) {
							yield return k;
						}
					}
				}
			}
			IEnumerator IEnumerable.GetEnumerator() {
				throw new NotImplementedException();
			}
		}
		public UiControlEnumable Ui_control_enumable;



		int new_name_number = 1;
		string getNewName() {
			while (true) {
				string name = $"新情节 {new_name_number++}";
				if (!Plots.ContainsKey(name)) {
					return name;
				}
			}
		}
		public event EventHandler eAddPlot;
		public event EventHandler eRemovePlot;
		public event EventHandler<TEventArgs<Novelview.NovelViewFeature>> eAddStoreLine;
		public event EventHandler<TEventArgs<Novelview.NovelViewFeature>> eRemoveStoreLine;
		public event EventHandler<TEventArgs<ControlKeeper>> eAddContolKeeper;
		internal void addPlot(Plot p) {
			Plots.Add(p.Name, p);
			eAddPlot?.Invoke(p,EventArgs.Empty);
		}
		internal void renamingPlor(Plot p,string new_name) {
			Plots.Remove(p.Name);
			Plots.Add(new_name, p);

		}
		internal void removePlot(Plot p) {
			Plots.Remove(p.Name);
			p.onRemoved();
			eRemovePlot?.Invoke(p, EventArgs.Empty);
		}
		public Plot cmdCreatePlot() {
			string title = getNewName();
			Plot plt=null;
			doCmd(() => {
				plt = new Plot(this, title);
			},() => {
				removePlot(plt);
			});
			return plt;
		}
		public bool Is_modifyed { get; private set; } = false;

		public bool undo() {
			Is_modifyed = true;
			return Cmd_record.undo();
		}

		public bool redo() {
			Is_modifyed = true;
			return Cmd_record.redo();
		}

		public void doCmd(Action c, Action ac) {
			Is_modifyed = true;
			Cmd_record.doCmd(c, ac);
		}

		/// <summary>
		/// 添加命令节点，调用undo命令会退回到上一个命令节点，所以一般在用户进行一次操作后，执行操作结果前，调用此函数。
		/// </summary>
		public void addCmdKnot() => Cmd_record.addCmdKnot();




		static readonly string special_head_name = "全局信息";
		static public Novel create() {
			var n = new Novel();
			n.Base_plot = new Plot(n,special_head_name, "新的故事");
			n.Plots.Remove(special_head_name);
			new Novelview.NovelViewFeature(n.Base_plot, "主线剧情");
			return n;
		}

		int new_storeline_number = 1;
		public void createStoreLine(string name = "支线剧情") {
			string store_name = name ;
			while (true) { 
				if (!Base_plot.Features.Exists(fea=>fea.Name == store_name)) {
					break;
				}
				store_name = $"{name} ({new_storeline_number})";
				new_storeline_number++;
			}
			addCmdKnot();
			Novelview.NovelViewFeature created_fea = null;
			doCmd(() => {
				created_fea = new Novelview.NovelViewFeature(Base_plot, store_name);
				eAddStoreLine?.Invoke(this, new TEventArgs<Novelview.NovelViewFeature>(created_fea));
				foreach (var ck in created_fea.Create_control_table) {
					eAddContolKeeper?.Invoke(this, new TEventArgs<ControlKeeper>(ck)) ;
				}
			}, () => {
				eRemoveStoreLine(this, new TEventArgs<Novelview.NovelViewFeature>(created_fea));
				Base_plot.Features?.Remove(created_fea); 
			});
		}


		static public Novel open(string in_path) {
			string all_string;
			try {
				all_string = File.ReadAllText(in_path);
			} catch (FileNotFoundException) {
				throw new PathException(string.Format("没有找到文件：{0}", in_path));
			} catch (DirectoryNotFoundException) {
				throw new PathException(string.Format("没有找到文件路径：{0}", in_path));
			} catch (PathTooLongException) {
				throw new PathException(string.Format("路径超过长度限制：{0}", in_path));
			}
			var n = new Novel();
			IFeature.parseFeatureString(all_string, 1, pares);
			void pares(string obj) {
				Plot plot  = Plot.readFromStr(n,obj, n.Feature_factory);
			}
			n.Base_plot = n.Plots[special_head_name];
			n.Plots.Remove(special_head_name);
			return n;
		}

		public void save(string out_path) {
			FileStream outFS = null;
			StreamWriter outStream = null;
			try {
				outFS = new FileStream(out_path, FileMode.Create, FileAccess.Write, FileShare.None);
			} catch (DirectoryNotFoundException) {
				throw new PathException(string.Format("没有找到输出文件路径：{0}", out_path));
			} catch (PathTooLongException) {
				throw new PathException(string.Format("文件路径过长了！", out_path));
			}
			outStream = new StreamWriter(outFS);
			outStream.Write(this.Base_plot.writeToStr());
			foreach (var plot in this.Plots.Values) {
				outStream.Write(plot.writeToStr());
			}
			outStream.Flush();
			outStream.Close();
			outStream = null;
			outFS.Close();
			outFS = null; 
			Is_modifyed = false;
		}
	}

	public class PathException : Exception {
		public PathException(string str) : base(str) { }
	}

	public class NovelException : Exception {
		public Plot postable_item;
		public NovelException(string str, Plot postable_item) : base(str) {
			this.postable_item = postable_item;
		}
	}
}
